module Movement

class Direction
  Forward=0 #facing screen
  Backward=1 #facing away from screen
  Left=2
  Right=3
end

class Action
  Standing = 0
  Walking = 1
  Running = 2 #no longer equivalent to walking ;)
  Dieing = 23
  DefAtk = 16
  Atk1 = 3
  Atk2 = 4
  Atk3 = 5
  Atk4 = 6
  Atk5 = 7
  Atk6 = 8
  Atk7 = 9
  Atk8 = 10
  DefBlock = 17 
  Special1 = 11
  Special2 = 12
  Special3 = 13
  Special4 = 14
  Special5 = 15
  Gen1 = 22
  Gen2 = 21
  Gen3 = 20
  Gen4 = 19
  Gen5 = 18
end

class Velocity
  Stationary=0
  Standing=0
  Walking=1
  Running=2
  SuperSpeed=3
end

#MovementBase is used for every NPCInstance or Player to facilitate movement. MovementBase is responsible for internal calculations (i.e. collision detection) as well as for animation
#In multiplayer modes, an alternative class may be used for the players.
class MovementBase
  attr_reader :state, :character, :speedaction, :customActions
  attr_reader :image, :anims, :window, :direction, :vel, :defVel, :frozen, :locked, :moveClass, :x, :y
  DirectionMap = {
    Direction::Forward => "F",
    Direction::Backward => "B",
    Direction::Left => "L",
    Direction::Right => "R",
  }
  ActionMap = {
    Action::Standing => "Stand",
    Action::Walking => "Walk",
    Action::Running => "Run",
    Action::Dieing => "Die",
    Action::Gen1 => "Gen1",
    Action::Gen2 => "Gen2",
    Action::Gen3 => "Gen3",
    Action::Gen4 => "Gen4",
    Action::Gen5 => "Gen5"
  }
  RActionMap = ActionMap.invert
  
  @@no_instances=0
  
  def initialize
    @x = @y = @vel = @defVel = @targetx = @targety = 0
    @targetid=nil
    @direction=Direction::Forward
    @action=Action::Standing
    @stickaction=false
    @pstickaction=false
    @frozen=false
    @locked=false
    @moveClass=@@no_instances%5
    @MTVars={}
    @@no_instances+=1
  end
  
  def warp(x, y)
    @x, @y = x, y
    tile.centerOn(@x, @y+@offsety)
  end
  
  def moveLeft
    @direction=Direction::Left
    @action=Action::Walking
    @vel=@defVel
    #@image = Gosu::Image.new(window, getImage(Left,@state,@type), false) #(decap)
  end
  
  def moveRight
    @direction=Direction::Right
    @action=Action::Walking
    @vel=@defVel
    #@image = Gosu::Image.new(window, getImage(Right,@state,@type), false) #(decap)
  end
  
  def moveForward
    @direction=Direction::Forward
    @action=Action::Walking
    @vel=@defVel
  end
  
  def moveBackward
    @direction=Direction::Backward
    @action=Action::Walking
    @vel=@defVel
  end
  
  def stand
    @action=Action::Standing
    @vel=Velocity::Stationary
  end
  
  def toggleSpeed(speed)
    @defVel=speed
  end
  
  def setTarget(char)
    if char!=nil
      @targetid=char.id
      @targetx, @targety = char.x, char.y
    else
      @targetid=nil
    end
  end
  
  def target
    return $window.characters[@targetid] unless @targetid==nil
    return nil
  end
  
  def freeze
    @frozen=true
  end
  def unfreeze
    @frozen=false
  end
  def lock(passive=false)
    @frozen=true
    if passive
      @locked=nil  
    else
      @locked=true
    end
    @lockdir=@direction
  end
  def unlock
    @frozen=false
    @locked=false
    @lockdir=nil
  end
  
  private
  def collide
    tile.each { |x,y|
      if not $window.map.collisionMap.traversable?(x,y)
        return true
      end
    }
    $window.characters.each { |char|
      if tile.id!=char.tile.id and tile.intersect?(char.tile) 
        return true  
      end
    }
    return false
  end
  
  public
  def collide?(x=@x,y=@y)
    tile.centerOn(x, y+@offsety)
    ret=collide
    tile.centerOn(@x, @y+@offsety)
    return ret
  end
  
  def move
    if @locked
      @direction=@lockdir
      return
    elsif @locked==nil
      @direction=@lockdir
    end
    if @frozen #or @vel==Velocity::Stationary
      return
    end
    #$trace.sput tile
    return autoMove if @rerouteMovement
    return autoPath if @pathmove
    case @direction
      when Direction::Forward       #y-coordinates are inverted by gosu engine - why?!
        @y+=@vel
      when Direction::Backward
        @y-=@vel
      when Direction::Left
        @x-=@vel
      when Direction::Right
        @x+=@vel
    end
    tile.centerOn(@x, @y+@offsety)
    if collide
      #undo move
      case @direction
        when Direction::Forward       #y-coordinates are inverted by gosu engine - why?!
          @y-=@vel
        when Direction::Backward
          @y+=@vel
        when Direction::Left
          @x+=@vel
        when Direction::Right
          @x-=@vel
      end
      tile.centerOn(@x, @y+@offsety)
    end
    setDefVel
  end
  
  def movePath(coordinates, velocity=Velocity::Walking, onfinish=nil, proccritical=false)
    @pathmove=true
    @path=coordinates
  end
  
  def movePath
    moveTo(@path[0][0], @path[0][1])
    @path.shift
    @pathmove=false if @path.size==0
  end
  
  def moveTo (x2, y2, velocity=Velocity::Walking, onfinish=nil, proccritical=false) #Bresenham style algo; see http://www.thescripts.com/forum/thread495550.html
    if x2==@x and y2==@y
      return
    end
    
    steep = (y2 - @y).abs > (x2 - @x).abs
    if steep
      if y2 - @y>0
        @direction=Direction::Forward
      else
        @direction=Direction::Backward
      end
    else
      if x2 - @x>1
        @direction=Direction::Right
      else
        @direction=Direction::Left
      end
    end
    
    if velocity <=Velocity::Walking
      permStickAction(Action::Walking)
    else
      permStickAction(Action::Running)
    end
    
    if (x2-@x)!=0
      m=(Float(y2-@y))/(Float(x2-@x))
      b=Float(@y-m*@x)
      n=(Float(Gosu::distance(@x,@y,x2,y2)))/(Float((x2-@x)*4))
     # puts m,(x2-@x),x2,@x,(y2-@y),y2,@y
      @MTVars[:vertical]=false
      @MTVars[:m]=m
      @MTVars[:b]=b
      @MTVars[:n]=n
    else
      @MTVars[:vertical]=true
      if y2-@y < 0
        @MTVars[:m]=-1
      else
        @MTVars[:m]=1
      end
    end
    
    @MTVars[:y2]=y2
    @MTVars[:x2]=x2
    @MTVars[:cx]=@x
    @MTVars[:cy]=@y
    @MTVars[:finproc]=onfinish
    @MTVars[:proccritical]=proccritical
    @MTVars[:velocity]=velocity
    @rerouteMovement=true
  end

  private
  def autoMove #Bresenham style algo; see http://www.thescripts.com/forum/thread495550.html
    if @x == @MTVars[:x2] and not @MTVars[:vertical]  
      unpermStickAction
      @rerouteMovement=false
      @MTVars[:finproc].call(self) if @MTVars[:finproc]!=nil
      @MTVars.clear
      return
    elsif @y == @MTVars[:y2] and @MTVars[:vertical]
      unpermStickAction
      @rerouteMovement=false
      @MTVars[:finproc].call(self) if @MTVars[:finproc]!=nil
      @MTVars.clear
      return
    end
    
    @vel=@MTVars[:velocity]
    
    if @MTVars[:vertical]    
      @MTVars[:cy]+=@MTVars[:velocity]*@MTVars[:m]
      y=@MTVars[:cy].round
      x=@x
    else
      @MTVars[:cx]=@MTVars[:cx]+(@MTVars[:n]*@MTVars[:velocity])
      @MTVars[:cy]=@MTVars[:m]*@MTVars[:cx]+@MTVars[:b]
      x=@MTVars[:cx].round
      y=@MTVars[:cy].round
    end
    
    tile.centerOn(x, y+@offsety)
    
    if collide
      tile.centerOn(@x, @y+@offsety)
      unpermStickAction
      @rerouteMovement=false    
      @MTVars[:finproc].call(self) if @MTVars[:finproc]!=nil and @MTVars[:proccritical]      
      @MTVars.clear
      return
    end
    
    @x,@y=x,y
    tile.centerOn(@x, @y+@offsety)
  end
  
  public
  def moving?
    return @rerouteMovement
  end
  
  def setDefVel
    @defVel=Velocity::Walking
  end
  
  def feedAction(name,stickaction=true)
    id=RActionMap[name]
    if id!=nil
      @action=id
      Animation.get("F"+name).restart
      Animation.get("B"+name).restart
      Animation.get("L"+name).restart
      Animation.get("R"+name).restart
      stickAction if stickaction
    elsif ActionBase.getAction(name)!=nil#self.customActions.index(name)!=nil
      @action=ActionBase.getAction(name)
      @action.restart
      stickAction if stickaction
    else
      return false
    end
    return true
  end
  
  private
  def linkAnims(linkfile)

  end
  def linkCustomActions(linkfile)

  end
  
  public
  def stickAction(action=@action)
    @action=action
    @lastaction=action
    @stickaction=true
  end
  def permStickAction(action=@action)
    if not @pstickaction
      @action=action
      @lastaction=action
      @pstickaction=true    
      return true
    else
      return false
    end
  end
  def unstickAction
    @stickaction=false
  end
  def unpermStickAction
    @pstickaction=false  
  end
  
  private
  def getAnim
    if @locked or @locked==nil
      return Animation.get(@anims[DirectionMap[@direction]+"Stand"]) 
    end
     
    @action=@lastaction if @stickaction or @pstickaction
     
    if @action.kind_of?(ActionBase) 
      @action.doAction(self)
      anim=@action.animation(DirectionMap[@direction])
      unstickAction if anim.done 
      return anim
    end
     
    if ActionMap[@action] !=nil
      return Animation.get(@anims[DirectionMap[@direction]+ActionMap[@action]])
    else
      $flog.fatal("Movement Base") {"Invalid animation resource pointer."}
      $flog.info("Movement Base") {"Bad Pointer: #{@direction},#{@action}"}
      return nil
    end
  end
  
  public
  def push(fromx, fromy)
    return if @x-fromx==0
    dy=(@y-fromy)
    dx=(@x-fromx)
    tx=@x
    ty=@y
    if dy.abs>dx.abs
      if dy.abs>0
        @direction=Direction::Forward
        ty+=5
      else
        @direction=Direction::Backward
        ty-=5
      end
    else
      if dx.abs>0
        @direction=Direction::Right
        tx+=5
      else
        @direction=Direction::Left
        tx-=5
      end
    end
    permStickAction(Action::Standing)
   # lock
    moveTo(tx,ty,Velocity::Walking, Proc.new { |this| this.unlock; this.unpermStickAction }, true)
  end
  
  def attack
    
  end
  
  def hit(amount)
    kill if @hp <=0
  end
  
  def kill
    freeze
    feedAction(DieAction.new(@anims[DirectionMap[@direction]+"Die"]))
  end
  
  def die
    
  end
  
  def draw(dx=@x,dy=@y)
    if @vel > @defVel and @action==Action::Walking
      @action=Action::Running #calculate whether to use the running animation
    end
    @image=getAnim
    if not @image.draw(dx-((@image.width/2).ceil),dy-((@image.height/2).ceil),ZOrder::Character)
      @stickaction=false
    end
  end
end

class ActionBase
  @@actions={}
  attr_reader :started
  def initialize(name, animation, params={})
    linkAnims(animation)
    @@actions[name]=self
    @started=false
  end
  def animation(dir)
    return Animation.get(@animation[dir+"Action"])
  end
  def linkAnims(file)
    @animation = {}
    DataFile.new(file).each(2) { |args|
      if args!=false
        @animation[args[0]]=args[1]
      end
    }
  end
  def restart
    @started=false
    Animation.get(@animation["FAction"]).restart
    Animation.get(@animation["BAction"]).restart
    Animation.get(@animation["LAction"]).restart
    Animation.get(@animation["RAction"]).restart
  end
  def doAction(character=nil)
    onStart(character) unless @started
    self.do(character)
    onFinish if Animation.get(@animation[MovementBase::DirectionMap[character.direction]+"Action"]).done
  end
  def do(character=nil)
    
  end
  def onStart(character=nil)
   
  end
  def onFinish(character=nil)
   
  end
  def ActionBase.actions
    return @@actions
  end
  def ActionBase.getAction(name)
    return @@actions[name]
  end
end

class ScriptAction < ActionBase
  def initialize(name, animation, script, params={})
    super(name, animation, params)
    @script=script
    @proc=RubyScript.new(@script).run
  end
  def do(character=nil)
    @proc.call(character)
  end
end

class NoMovement
  def initialize
    @x, @y=0
    @direction=Direction::Forward
    @action=Action::Standing
    @frozen=false
    @locked=false
  end
  
  def warp(x, y)
    @x, @y = x, y
    tile.centerOn(@x, @y+@offsety)
  end
  
  def linkAnims(file)
    
  end
  def linkImage(file)
    
  end
  
  def setDirection(dir)
    @direction=Direction::Forward
  end
  
  private
  def collide
    tile.each { |x,y|
      if not $window.map.collisionMap.traversable?(x,y)
        return true
      end
    }
    $window.characters.each { |char|
      if tile.id!=char.tile.id and tile.intersect?(char.tile) 
        return true  
      end
    }
    return false
  end
  
  public
  def collide?(x=@x,y=@y)
    tile.centerOn(x, y+@offsety)
    ret=collide
    tile.centerOn(@x, @y+@offsety)
    return ret
  end
  
  def move; end
  
  def hit; end
  def push; end
  def kill; end
  
  def moving?
    return false
  end
  
  def draw
    @image.draw(dx-((@image.width/2).ceil),dy-((@image.height/2).ceil),ZOrder::Character)
  end
end

end #module Movement

include Movement

class Camera
  attr_accessor :x,:y
  def initialize(x=0,y=0)
    @x=x
    @y=y
  end
end

